package com.hyt.it.ogt.kq.service.gov.roomdata;

import java.io.File;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.examsys.gce.GCEExamTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.ExamClassCodeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.FileTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.TaskConfigCodeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.Constants;
import com.hyt.it.ogt.kq.common.gov.utils.DateTimeUtil;
import com.hyt.it.ogt.kq.common.gov.utils.FileUtil;
import com.hyt.it.ogt.kq.common.gov.utils.XmlUtil;
import com.hyt.it.ogt.kq.common.gov.utils.ZipUtil;
import com.hyt.it.ogt.kq.service.gov.feign.tps.GovTpsClient;
import com.hyt.it.ogt.kq.service.gov.model.dto.PlaceAreaInfoDTO;
import com.hyt.it.ogt.kq.service.gov.model.entity.GceRoomData;
import com.hyt.it.ogt.kq.service.gov.model.entity.ObtRoomData;
import com.hyt.it.ogt.kq.service.gov.model.entity.SysFile;
import com.hyt.it.ogt.kq.service.gov.model.entity.Task;
import com.hyt.it.ogt.kq.service.gov.model.entity.TaskArea;
import com.hyt.it.ogt.kq.service.gov.model.entity.TaskConfig;
import com.hyt.it.ogt.kq.service.gov.model.entity.TaskMockRoomData;
import com.hyt.it.ogt.kq.service.gov.model.entity.Time;
import com.hyt.it.ogt.kq.service.gov.model.entity.TimeRoom;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceBehaviorAnalysisConfig;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceCandidate;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceExamData;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceExamInfo;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceSeat;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceSubject;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceSubjectModule;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.gce.GceTime;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.ExamData;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.ExamInfo;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.ExamTime;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.Module;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.ObtCandidate;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.ObtSubject;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.RoomDataInfo;
import com.hyt.it.ogt.kq.service.gov.roomdata.model.obt.Seat;
import com.hyt.it.ogt.kq.service.gov.service.ISysFileService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskAreaService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskConfigService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskMockRoomDataService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskPlaceService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeRoomCandidateService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeRoomService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeSubjectService;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;
import com.hyt.model.tps.vo.Bucket;
import com.obs.services.model.ObjectMetadata;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 考务包服务实现
 *
 * @author 刘恒活
 * @since 2021/9/6 15:23
 */
@Slf4j
@Service
public class RoomDataService {

    @Resource
    private ITimeSubjectService iTimeSubjectService;

    @Resource
    private ITimeRoomCandidateService iTimeRoomCandidateService;

    @Resource
    private ITaskService iTaskService;

    @Resource
    private ITaskConfigService iTaskConfigService;

    @Resource
    private ITaskAreaService iTaskAreaService;

    @Resource
    private ITimeService iTimeService;

    @Resource
    private CloudObsService cloudObsService;

    @Resource
    private GovTpsClient tpsClient;

    @Resource
    private ConfigManager configManager;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ISysFileService iSysFileService;

    @Resource
    private ITimeRoomService iTimeRoomService;

    @Resource
    private ITaskPlaceService iTaskPlaceService;

    @Resource
    private ITaskMockRoomDataService iTaskMockRoomDataService;

    /**
     * @param taskId   任务
     * @param obtRoomData 场次
     * @param officeId 机构id
     * @return 结果
     * @throws Exception 异常
     */
    @Async("executor")
    public CompletableFuture<Boolean> createObtRoomDataByTimeRoom(String taskId, ObtRoomData obtRoomData, String officeId, String progressKey, long progressKeyExpireSeconds) {
        log.info("------开始多线程{}生成OBT考务包：{}--------", Thread.currentThread().getName(), taskId);
        Task task = iTaskService.getById(taskId);
        // 本次生成考务包版本号
        String version = DateTimeUtil.getNowString();
        // 定义考务包生成根目录
        File rootDir = new File(StringUtils.join(configManager.getPlaceData(), UUIDUtils.newSortUUID(), File.separator));
        if (!rootDir.mkdirs()) {
            log.debug("目录创建失败：{}", rootDir.getAbsolutePath());
        }
        // 查询考试基础配置
        Map<String, String> configMap = iTaskConfigService.getByTaskId(task.getId()).stream()
                .collect(Collectors.toMap(TaskConfig::getCode, TaskConfig::getValue));
        ExamData examData = new ExamData();
        examData.setVer(version);
        examData.setSysVer(configMap.get(TaskConfigCodeEnum.EXAM_VERSION.getCode()));
        examData.setExamInfo(this.buildExamInfo(task, configMap, obtRoomData));
        examData.setSubjectModuleList(this.getSubjectModuleList());
        examData.setSubjectList(this.getSubjectList(taskId, obtRoomData));
        examData.setExamTimesList(this.getExamTimesList(taskId, version, obtRoomData));
        examData.setSeatList(this.getSeatList(taskId, obtRoomData));
        examData.setCandidateList(this.getCandidateList(task, obtRoomData, rootDir, officeId));
        // 打包并上传obs
        RoomDataInfo dataInfo = this.packAndUpload(examData, rootDir, obtRoomData, task);
        // 保存相关数据信息
        this.saveRoomDataPackageInfo(obtRoomData, dataInfo);
        // 进度条
        redisTemplate.opsForValue().increment(progressKey, 1);
        redisTemplate.expire(progressKey, progressKeyExpireSeconds, TimeUnit.SECONDS);
        log.info("------结束多线程{}生成OBT考务包：{}--------", Thread.currentThread().getName(), taskId);
        return CompletableFuture.completedFuture(true);
    }

    /**
     * 按照场次生成GCE考试系统考务包
     * @param task 考试任务
     * @param gceRoomData 场次数据
     * @param officeId 机构id
     * @param progressKey 进度条key
     * @param progressKeyExpireSeconds 进度条key过期时间
     * @param configMap 考试配置
     * @param tempDirectory 临时目录
     * @param type 考务包类型
     * @return 结果
     */
    @Async("executor")
    public CompletableFuture<Boolean> createGceRoomDataByTimeRoom(Task task, GceRoomData gceRoomData, String officeId, String progressKey, long progressKeyExpireSeconds,
                                                                  File tempDirectory, Map<String, String> configMap, String type) {
        log.info("------开始多线程{}生成GCE考务包：{}--------", Thread.currentThread().getName(), task.getId());
        //创建当前场次考务包临时目录
        File timeRoomDirectory = new File(tempDirectory.getAbsolutePath() + File.separator + gceRoomData.getTimeRoomId());
        //本次生成考务包版本号
        String timestamp = DateTimeUtil.getNowString();
        //生成GCE考试系统考务包xml文件数据
        GceExamData gceExamData = this.createGceExamData(timestamp, configMap);
        //生成GCE考试系统考务包xml文件ExamInfo节点
        this.createGceExamInfo(gceExamData, task, configMap, gceRoomData, type);
        //生成GCE考试系统考务包xml文件StudentList节点，并生成考生照片目录及照片文件
        this.createGceStudentList(task, gceRoomData, gceExamData, timeRoomDirectory, officeId);
        //生成GCE考试系统考务包xml文件SubjectModuleList节点
        this.createGceSubjectModuleList(gceExamData);
        //生成GCE考试系统考务包xml文件SubjectList节点
        this.createGceSubjectList(task, gceRoomData, gceExamData);
        //生成GCE考试系统考务包xml文件ExamTimesList节点
        this.createGceExamTimesList(task, gceRoomData, gceExamData, timestamp);
        //生成GCE考试系统考务包xml文件SeatList节点
        this.createGceSeatList(task, gceRoomData, gceExamData);
        //打包上传至华为云OBS
        SysFile sysFile = this.uploadGceRoomDataPackage(task, gceRoomData, gceExamData, timeRoomDirectory);
        //保存考务包文件信息至数据库
        this.saveGceRoomDataPackageInfo(sysFile, gceRoomData, timestamp, task, type);
        //进度条
        redisTemplate.opsForValue().increment(progressKey, 1);
        redisTemplate.expire(progressKey, progressKeyExpireSeconds, TimeUnit.SECONDS);
        log.info("------结束多线程{}生成GCE考务包：{}--------", Thread.currentThread().getName(), task.getId());
        return CompletableFuture.completedFuture(true);
    }

    /**
     * 生成GCE考试系统考务包xml文件数据
     * @param timestamp 时间戳
     * @param configMap 考试配置
     * @return GCE考试系统考务包xml文件数据
     */
    private GceExamData createGceExamData(String timestamp, Map<String, String> configMap) {
        GceExamData gceExamData = new GceExamData();
        //时间戳
        gceExamData.setTimestamp(timestamp)
                //考试系统版本号
                .setExamSystemVersion(configMap.get(TaskConfigCodeEnum.EXAM_VERSION.getCode()));
        return gceExamData;
    }

    /**
     * 生成GCE考试系统考务包xml文件ExamInfo节点
     * @param gceExamData 考试信息数据
     * @param task 考试任务数据
     * @param configMap 考试配置数据
     * @param gceRooData 场次数据
     */
    private void createGceExamInfo(GceExamData gceExamData, Task task, Map<String, String> configMap,
                                   GceRoomData gceRooData, String type) {
        GceExamInfo gceExamInfo = new GceExamInfo();

        //考点区域信息参数：
        //获取场次区域信息
        PlaceAreaInfoDTO placeAreaInfoDTO = iTaskPlaceService.getPlaceAreaInfo(gceRooData.getPlaceId());
        //省份编号、省份名称、市编号、市名称、区县编号、区县名称
        BeanUtils.copyProperties(placeAreaInfoDTO, gceExamInfo);

        //考点考场信息参数：
        //考点编号、考点名称、考场编号、考场名称、考场类型、机位数、备用机位数、考场地址
        BeanUtils.copyProperties(gceRooData, gceExamInfo);

        //考试任务信息参数：
        //考试任务编号
        gceExamInfo.setTaskCode(task.getCode())
                //考试任务名称
                .setTaskName(task.getName())
                //考试类型：1：正式考试，2：模拟考试
                .setExamType(type)
                //考试任务类型，1：正式，2：仿真，3：模拟
                .setTaskType(String.valueOf(task.getType()))
                //考试年份
                .setYear(String.valueOf(task.getBeginDate().getYear()))

                //考试系统配置参数：
                //加时分钟数，单位分钟
                .setOvertimeLimit(configMap.get(TaskConfigCodeEnum.ADD_TIME_LIMIT.getCode()))
                //允许迟到时间(单位分钟， 0为不允许迟到)
                .setLateTime(configMap.get(TaskConfigCodeEnum.LATE_TIME.getCode()))
                //允许提前交卷时间(单位分钟，0为不允许提前交卷)
                .setHandInTime(configMap.get(TaskConfigCodeEnum.AHEAD_SUBMIT_TIME.getCode()))
                //允许整场考试中断的分钟数
                .setInterruptTime(configMap.get(TaskConfigCodeEnum.BREAK_TIME.getCode()))
                //允许推迟开考分钟数
                .setPutOffTime(configMap.get(TaskConfigCodeEnum.WAIT_START_EXAM.getCode()))
                //允许提前开考分钟数
                .setAheadTime(configMap.get(TaskConfigCodeEnum.AHEAD_TIME.getCode()))
                //解锁屏密码
                .setUnlockPassword(configMap.get(TaskConfigCodeEnum.UNDO_PASSWORD.getCode()))
                // 上传考试结果包含项，用于签到功能，暂时写死为空字符串
                .setResultContain(StringUtils.EMPTY)
                //上传考试过程状态数据周期
                .setProcessReportFrequency(configMap.get(TaskConfigCodeEnum.UPLOAD_DATA_PERIOD.getCode()))
                //考试类型编号（详见'GCEExamClassCodeEnum.java'）
                .setExamClassCode(configMap.get(TaskConfigCodeEnum.EXAM_CLASS_CODE.getCode()))
                //是否可以访问外网1.可以访问；2.不可以访问
                .setInternetAccess(configMap.get(TaskConfigCodeEnum.IS_OPEN_EXTRANET_VISIT.getCode()))
                //考生访问外网整场密码
                .setInternetAccessPassword(configMap.get(TaskConfigCodeEnum.THROUGHOUT_THE_PWD.getCode()))
                //考生客户端访问外网密码
                .setClientInternetAccessPassword(configMap.get(TaskConfigCodeEnum.EXAMINEE_CLIENT_VISIT_PWD.getCode()))
                //作答预警时间段（分钟）
                .setRespondWarningFrequency(configMap.get(TaskConfigCodeEnum.REPLY_PERIOD.getCode()))
                //作答时间段预警答题数
                .setRespondItemWarningAmount(configMap.get(TaskConfigCodeEnum.REPLY_NUM.getCode()))
                //未作答预警时间段（分钟）
                .setNoneRespondWarningFrequency(configMap.get(TaskConfigCodeEnum.UN_REPLY_PERIOD.getCode()))
                //是否签到后允许登陆
                .setLoginAfterSign(configMap.get(TaskConfigCodeEnum.Is_Login_After_Sign.getCode()))
                //拦截其他应用
                .setBlockOtherApp(configMap.get(TaskConfigCodeEnum.BLOCK_OTHER_APP.getCode()))

                //行为分析参数：
                //是否启用行为分析规则
                .setEnableBehaviorAnalysis(configMap.get(TaskConfigCodeEnum.ENABLE_BEHAVIOR.getCode()))
                //行为分析规则规定，详见'TaskConfigCodeEnum.java'和'GceBehaviorAnalysisConfig.java'
                .setBehaviorAnalysisConfig(this.createGceBehaviorAnalysisConfig(configMap));

                //暂时用不上的参数：
                //PublicKey，暂时用不上（用途未知）
                //FTP地址

        gceExamData.setGceExamInfo(gceExamInfo);
    }

    private void createGceStudentList(Task task, GceRoomData gceRoomData, GceExamData gceExamData,
                                      File timeRoomDirectory, String officeId) {
        //获取GCE考务包考生信息
        List<GceCandidate> gceCandidateList = iTimeRoomCandidateService.getGceCandidateList(task.getId(), gceRoomData.getTimeRoomId());
        //创建考生照片目录
        File photoDirectory = new File(timeRoomDirectory.getAbsolutePath() + File.separator + Constants.PHOTO);
        if (!photoDirectory.mkdirs()) {
            log.debug("GCE场次考务包考生照片目录创建失败，路径: {}", photoDirectory.getAbsolutePath());
            throw new KqException(ResponseCode.ERROR_ROOM_DATA_CREATE.getCode(),
                    "GCE考务包考生照片目录创建失败");
        }
        //获取腾讯云COS的Domain
        ApiResponse<Bucket> tempCredentialResponse = tpsClient.getTencentCosTempCredential(officeId);
        if (tempCredentialResponse.getCode() != 1) {
            //获取腾讯云COS临时凭证失败
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_CANDIDATE_PHOTO_EXCEPTION.getCode(),
                    "获取腾讯云COS临时凭证失败");
        }
        String domain = tempCredentialResponse.getData().getDomain();
        //生成考生照片信息数据并将考生照片下载至考生照片目录中
        gceCandidateList.forEach(candidate -> {
            // 暂时不支持残障特殊人群功能，暂时写死为"0"
            candidate.setSpecialHelp("0");
            if("0".equals(candidate.getIdType())){ // 跟机考证件类型保持一致 1代表身份证 2军官证3港居民证4澳居民证5护照6台胞证7学籍号
                candidate.setIdType("1");
            } else if("1".equals(candidate.getIdType())){
                candidate.setIdType("3");
            } else {
                candidate.setIdType("5");
            }
            //存在考生照片则下载考生照片
            if (StringUtils.isNotBlank(candidate.getPhotoFileName())) {
                try {
                    //获取考生照片全路径
                    String photoUrl = candidate.getPhotoFileName();
                    //获取考生照片半路径
                    String photoPath = photoUrl.replace(domain, StringUtils.EMPTY);
                    //从腾讯云COS下载考生照片数据
                    ApiResponse<byte[]> response = tpsClient.downloadTencentCosFile(officeId, photoPath);
                    if (response.getCode() != 1) {
                        log.error("从腾讯云COS下载考生照片数据失败: {}, {}",
                                candidate.getAdmissionNumber(), photoPath);
                        throw new KqException(ResponseCode.ERROR_ROOM_DATA_CREATE.getCode(),
                                "从腾讯云COS下载考生照片数据失败");
                    }
                    //写入本地文件
                    byte[] bytes = response.getData();
                    String photoFileName = candidate.getAdmissionNumber() + Constants.JPG;
                    File photoFile = new File(photoDirectory, photoFileName);
                    cn.hutool.core.io.FileUtil.writeBytes(bytes, photoFile);
                    //修改考生数据中的考生照片名称
                    candidate.setPhotoFileName(photoFileName);
                } catch (Exception e) {
                    log.error("生成GCE考务包时下载考生照片异常（忽略异常）", e);
                    candidate.setPhotoFileName(StringUtils.EMPTY);
                }
                return;
            }
            //不存在考生照片则直接填充空字符串
            candidate.setPhotoFileName(StringUtils.EMPTY);
        });
        gceExamData.setGceCandidateList(gceCandidateList);
    }

    /**
     * 生成GCE行为分析配置数据
     * @param configMap 考试配置
     * @return GCE行为分析配置数据
     */
    private String createGceBehaviorAnalysisConfig(Map<String, String> configMap) {
        GceBehaviorAnalysisConfig gceBehaviorAnalysisConfig = new GceBehaviorAnalysisConfig();
        //有效时间点之后，浏览小题数，超过N道
        gceBehaviorAnalysisConfig.setExamStartWithinMinute(Integer.parseInt(configMap.get(TaskConfigCodeEnum.EXAM_START_WITHIN_MINUTE.getCode())))
                //规则有效时间点，如N分钟之后
                .setNothingOperateMinute(Integer.parseInt(configMap.get(TaskConfigCodeEnum.NOTHING_OPERATE_MINUTE.getCode())))
                //不按正常题号作答，超过N道有效时间点之后
                .setAnswerTimeBelow(Integer.parseInt(configMap.get(TaskConfigCodeEnum.ANSWER_TIME_BELOW.getCode())))
                //有效时间点之后，某个时间段，单位分钟
                .setAnsweredQuestionPercent(Integer.parseInt(configMap.get(TaskConfigCodeEnum.ANSWERED_QUESTION_PERCENT.getCode())))
                //有效时间点之后，某个时间段内浏览和作答的比例
                .setExamStartAfterMinute(Integer.parseInt(configMap.get(TaskConfigCodeEnum.EXAM_START_AFTER_MINUTE.getCode())))
                //作答小题的最大数量
                .setUnansweredBrowserPercent(Integer.parseInt(configMap.get(TaskConfigCodeEnum.UNANSWERED_BROWSER_PERCENT.getCode())))
                //开始考试之后，有效时间点之前，浏览和作答的比例
                .setAnsweredPercent(Integer.parseInt(configMap.get(TaskConfigCodeEnum.ANSWERED_PERCENT.getCode())))
                //按照试卷顺序作答，超过N道
                .setBrowserOrAnswerCount(Integer.parseInt(configMap.get(TaskConfigCodeEnum.BROWSER_OR_ANSWER_COUNT.getCode())))

                .setBrowseQuestionCount(Integer.parseInt(configMap.get(TaskConfigCodeEnum.BROWSE_QUESTION_COUNT.getCode())))

                .setWithinMinute(Integer.parseInt(configMap.get(TaskConfigCodeEnum.WITHIN_MINUTE.getCode())))

                .setBrowseQuestionPercent(Integer.parseInt(configMap.get(TaskConfigCodeEnum.BROWSE_QUESTION_PERCENT.getCode())))

                .setModifyAnswerPercent(Integer.parseInt(configMap.get(TaskConfigCodeEnum.MODIFY_ANSWER_PERCENT.getCode())));

        return JSONObject.toJSONString(gceBehaviorAnalysisConfig, SerializerFeature.WriteMapNullValue);
    }

    private void createGceSubjectModuleList(GceExamData gceExamData) {
        GceSubjectModule gceSubjectModule = new GceSubjectModule();
        List<GceSubjectModule> gceSubjectModuleList = new ArrayList<>();
        gceSubjectModuleList.add(gceSubjectModule);
        gceExamData.setGceSubjectModuleList(gceSubjectModuleList);
    }

    private void createGceSubjectList(Task task, GceRoomData gceRoomData, GceExamData gceExamData) {
        List<GceSubject> gceSubjectList = iTimeSubjectService.getGceSubjectList(task.getId(), gceRoomData.getTimeId());
        gceExamData.setGceSubjectList(gceSubjectList);
    }

    private void createGceExamTimesList(Task task, GceRoomData gceRoomData, GceExamData gceExamData, String timestamp) {
        //获取批次数据
        List<Time> timeList = iTimeService.lambdaQuery()
                .eq(Time::getTaskId, task.getId())
                .eq(Time::getId, gceRoomData.getTimeId())
                .eq(Time::getDelFlag, false)
                .orderByAsc(Time::getCode)
                .list();
        //批次数据转换为考务包批次信息数据
        List<GceTime> gceTimeList = timeList.stream().map(time -> {
            GceTime gceTime = new GceTime();
            //场次编号
            gceTime.setTimeRoomCode(gceRoomData.getTimeRoomCode())
                    //批次编号
                    .setCode(String.valueOf(time.getCode()))
                    //批次名称
                    .setName(time.getName())
                    //批次日期，格式：yyyy-MM-dd
                    .setDate(time.getStartTime().format(Constants.YMD))
                    //批次开始时间，格式：HH:mm
                    .setStartTime(time.getStartTime().format(Constants.HM))
                    //批次结束时间，格式：HH:mm
                    .setEndTime(time.getEndTime().format(Constants.HM))
                    // 用途未知，暂时写死为空字符串
                    .setPassword(StringUtils.EMPTY)
                    // 语种，暂时写死为"1"
                    .setLanguage("1")
                    //时间戳
                    .setTimestamp(timestamp);
            return gceTime;
        }).collect(Collectors.toList());
        gceExamData.setGceTimeList(gceTimeList);
    }

    private void createGceSeatList(Task task, GceRoomData gceRoomData, GceExamData gceExamData) {
        List<GceSeat> gceSeatList = iTimeRoomCandidateService.getGceSeatList(task.getId(), gceRoomData.getTimeRoomId());
        gceExamData.setGceSeatList(gceSeatList);
    }

    @SneakyThrows
    private SysFile uploadGceRoomDataPackage(Task task, GceRoomData gceRoomData, GceExamData gceExamData, File timeRoomDirectory) {
        //创建当前场次考务包的XML文件
        File xmlFile = new File(timeRoomDirectory, Constants.XML);
        if (!xmlFile.createNewFile()) {
            log.error("创建场次考务包的XML文件失败，路径: {}", xmlFile.getAbsolutePath());
            throw new KqException(ResponseCode.ERROR_ROOM_DATA_CREATE.getCode(),
                    "创建场次考务包的XML文件失败");
        }
        //将考务包XML对象序列化为字符串
        String xmlContent = XmlUtil.toXmlWithNoneEscapeAndHeader(gceExamData);
        //将考务包XML字符串写入XML文件中
        XmlUtil.writeFile(xmlContent, xmlFile);
        //将XML文件和考生照片目录打包为JKP文件
        String jkpFileName = gceRoomData.getPlaceName() + "_" + gceRoomData.getRoomName()
                + "_" + gceRoomData.getTimeRoomCode() + Constants.JKP;
        File jkpFile = new File(timeRoomDirectory, jkpFileName);
        ZipUtil.zipFolderNotIncludeRootFolderWithPassword(jkpFile.getAbsolutePath(), timeRoomDirectory,
                configManager.getGceRoomDataPackagePassword());
        //将JKP文件打包为zip包文件
        String zipFileName = StringUtils.joinWith(Constants.SEP, gceExamData.getGceExamInfo().getCityName(),
                gceExamData.getGceExamInfo().getPlaceName(), gceExamData.getGceExamInfo().getRoomName(),
                gceRoomData.getTimeCode(), gceRoomData.getTimeRoomCode() + Constants.ZIP);
        File zipFile = new File(timeRoomDirectory, zipFileName);
        ZipUtil.zip(jkpFile, zipFile.getAbsolutePath());
        //将打包好的zip包文件上传至华为云OBS
        String path = StringUtils.join(configManager.getCloudRootPath(), task.getCode(),
                Constants.ROOM_DATA, DateTimeUtil.getNowString(), "/", zipFileName);
        String url = cloudObsService.updateFile(path, zipFile);
        //获取上传到华为云OBS上的文件的文件信息
        ObjectMetadata objectMetadata = cloudObsService.getFileMetaData(path);
        //生成考务包文件信息
        SysFile sysFile = new SysFile();
        sysFile.setName(zipFileName)
                .setPath(path)
                .setUrl(url)
                .setSize(objectMetadata.getContentLength())
                .setType(FileTypeEnum.FILE_ROOM_DATA_PACKAGE.getName())
                .setMd5(objectMetadata.getEtag().replaceAll("\"", StringUtils.EMPTY));
        return sysFile;
    }

    private void saveGceRoomDataPackageInfo(SysFile sysFile, GceRoomData gceRoomData,
                                            String version, Task task, String type) {
        //保存考务包文件信息
        if (!iSysFileService.save(sysFile)) {
            log.error("保存考务包文件信息失败");
            throw new KqException(ResponseCode.ERROR_ROOM_DATA_CREATE.getCode(),
                    "保存考务包文件信息失败");
        }
        //生成正式考试考务包，保存正式考务包信息
        if (GCEExamTypeEnum.FORMAL.getType().equals(type)) {
            //更新考务包文件id
            TimeRoom timeRoom = new TimeRoom();
            timeRoom.setId(gceRoomData.getTimeRoomId());
            timeRoom.setRoomDataFileId(sysFile.getId());
            timeRoom.setRoomDataVersion(version);
            iTimeRoomService.updateById(timeRoom);
            return;
        }
        //生成模拟测试考务包，保存模拟测试考务包信息
        TaskMockRoomData taskMockRoomData = new TaskMockRoomData();
        taskMockRoomData.setTaskId(task.getId())
                .setTimeRoomId(gceRoomData.getTimeRoomId())
                .setFileId(sysFile.getId())
                .setVersion(version);
        if (!iTaskMockRoomDataService.save(taskMockRoomData)) {
            log.error("保存模拟测试考务包信息失败");
            throw new KqException(ResponseCode.ERROR_ROOM_DATA_CREATE.getCode(),
                    "保存模拟测试考务包信息失败");
        }
    }

    /**
     * 保存上传文件信息和更新考务包file_id
     *
     * @param obtRoomData     场次
     * @param roomDataInfo 考务包信息
     */
    public void saveRoomDataPackageInfo(ObtRoomData obtRoomData, RoomDataInfo roomDataInfo) {
        SysFile sysFile = new SysFile();
        sysFile.setUrl(roomDataInfo.getObsUrl());
        sysFile.setPath(roomDataInfo.getObsPath());
        sysFile.setName(roomDataInfo.getPackageName());
        sysFile.setType(FileTypeEnum.FILE_ROOM_DATA_PACKAGE.getName());
        sysFile.setMd5(roomDataInfo.getObsFileMd5());
        sysFile.setSize(roomDataInfo.getObsFileSize());
        // 保存文件信息
        iSysFileService.save(sysFile);
        TimeRoom timeRoom = new TimeRoom();
        timeRoom.setId(obtRoomData.getTimeRoomId());
        timeRoom.setRoomDataFileId(sysFile.getId());
        timeRoom.setUpdateDate(LocalDateTime.now());
        // 更新考务包状态
        iTimeRoomService.updateById(timeRoom);
    }

    /**
     * 打包并上传obs
     *
     * @param examData 考试配置
     * @param rootDir  跟目录
     */
    @SneakyThrows
    private RoomDataInfo packAndUpload(ExamData examData, File rootDir, ObtRoomData obtRoomData, Task task) {
        RoomDataInfo roomDataInfo = new RoomDataInfo();
        // 生成xml
        XmlUtil.toXmlFile(examData, rootDir.getAbsolutePath() + File.separator + Constants.XML);
        // 打jkp包
        File jkpFile = new File(rootDir.getAbsolutePath() + File.separator + obtRoomData.getRoomName()
                + "_" + obtRoomData.getPlaceCode() + Constants.JKP);
        ZipUtil.zipFolderNotIncludeRootFolderWithPassword(jkpFile.getAbsolutePath(), rootDir,
                configManager.getPlaceDataPassword());
        // 打zip包
        String fileName = StringUtils.joinWith(Constants.SEP, examData.getExamInfo().getCityName(),
                examData.getExamInfo().getExamPlaceName(), examData.getExamInfo().getExamRoomName(),
                obtRoomData.getTimeCode(), obtRoomData.getOrderId() + Constants.ZIP);
        File targetFile = new File(rootDir.getAbsolutePath() + File.separator + fileName);
        ArrayList<File> fileList = new ArrayList<>();
        fileList.add(jkpFile);
        ZipUtil.zip(fileList, targetFile.getAbsolutePath());
        // 上传华为云
        String path = StringUtils.join(configManager.getCloudRootPath(), task.getCode(),
                Constants.ROOM_DATA, DateTimeUtil.getNowString(), "/", fileName);
        String url = cloudObsService.updateFile(path, targetFile);
        ObjectMetadata fileMetaData = cloudObsService.getFileMetaData(path);
        roomDataInfo.setCreateTime(LocalDateTime.now());
        roomDataInfo.setPackageName(fileName);
        roomDataInfo.setObsUrl(url);
        roomDataInfo.setObsPath(path);
        roomDataInfo.setObsFileSize(fileMetaData.getContentLength());
        roomDataInfo.setObsFileMd5(FileUtil.getFileMd5(targetFile));
        if (configManager.getIsDeleteTempDir()) {
            // 上传完成，删除临时目录
            try {
                FileUtils.deleteDirectory(rootDir);
            } catch (Exception e) {
                log.error("生成考务包时，删除临时目录异常，{}", rootDir.getAbsolutePath());
            }
        }
        return roomDataInfo;
    }

    private List<ObtCandidate> getCandidateList(Task task, ObtRoomData obtRoomData, File rootDir, String officeId) {
        List<ObtCandidate> candidates = iTimeRoomCandidateService.getCandidateList(task.getId(), obtRoomData.getTimeRoomId());
        File file = new File(rootDir.getAbsolutePath() + File.separator + Constants.PHOTO);
        if (!file.mkdirs()) {
            log.debug("目录创建失败：{}", file.getAbsolutePath());
        }
        //获取腾讯云COS的Domain
        ApiResponse<Bucket> tempCredentialResponse = tpsClient.getTencentCosTempCredential(officeId);
        if (tempCredentialResponse.getCode() != 1) {
            //获取腾讯云COS临时凭证失败
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_CANDIDATE_PHOTO_EXCEPTION.getCode(),
                    "获取腾讯云COS临时凭证失败");
        }
        String domain = tempCredentialResponse.getData().getDomain();
        for (ObtCandidate item : candidates) {
            if (StringUtils.isNotBlank(item.getPhoto())) {
                String photoUrl = item.getPhoto();
                //获取考生照片半路径
                String photoPath = photoUrl.replace(domain, StringUtils.EMPTY);
                try {
                    //从腾讯云COS下载考生照片数据
                    ApiResponse<byte[]> response = tpsClient.downloadTencentCosFile(officeId, photoPath);
                    if (response.getCode() != 1) {
                        log.error("从腾讯云COS下载考生照片数据失败: {}, {}",
                                item.getStudentId(), photoPath);
                        throw new KqException(ResponseCode.ERROR_DOWNLOAD_CANDIDATE_PHOTO.getCode(),
                                ResponseCode.ERROR_DOWNLOAD_CANDIDATE_PHOTO.getMsg());
                    }
                    //写入本地文件
                    byte[] bytes = response.getData();
                    String fileNamePath = item.getStudentId() + Constants.JPG;
                    String target = file.getAbsolutePath() + File.separator + fileNamePath;
                    FileUtil.bytesToFile(bytes, target);
                    item.setPhoto(fileNamePath);
                } catch (Exception e) {
                    log.error("生成考务包时下载考生照片异常（忽略异常）：{}",
                            e.getMessage());
                    item.setPhoto("");
                }
            } else {
                item.setPhoto("");
            }
        }
        return candidates;
    }

    /**
     * obt和gce不一样，估计需要区分，后期再整
     *
     * @param task      任务
     * @param configMap 考试配置
     * @param obtRoomData  场次
     * @return 考试信息
     */
    private ExamInfo buildExamInfo(Task task, Map<String, String> configMap, ObtRoomData obtRoomData) {
        ExamInfo examInfo = new ExamInfo();
        examInfo.setExamCode(task.getCode());
        examInfo.setExamName(task.getName());
        examInfo.setExamYear(task.getBeginDate().getYear() + "");
        examInfo.setExamPlaceNo(obtRoomData.getPlaceCode());
        examInfo.setExamPlaceName(obtRoomData.getPlaceName());
        TaskArea province = iTaskAreaService.getOne(Wrappers.<TaskArea>lambdaQuery()
                .eq(TaskArea::getAreaId, obtRoomData.getProvince()).last("limit 1"));
        examInfo.setProvinceName(province.getName());
        examInfo.setProvinceNo(obtRoomData.getProvince());
        TaskArea city = iTaskAreaService.getOne(Wrappers.<TaskArea>lambdaQuery()
                .eq(TaskArea::getAreaId, obtRoomData.getCity()).last("limit 1"));
        examInfo.setCityName(city.getName());
        examInfo.setCityNo(obtRoomData.getCity());
        TaskArea country = iTaskAreaService.getOne(Wrappers.<TaskArea>lambdaQuery()
                .eq(TaskArea::getAreaId, obtRoomData.getDistrict()).last("limit 1"));
        // 县级
        examInfo.setCountyName(country.getName());
        examInfo.setCountyNo(obtRoomData.getDistrict());

        examInfo.setExamRoomNo(obtRoomData.getRoomCode());
        examInfo.setExamRoomName(obtRoomData.getRoomName());
        // <!--座位总数-->
        examInfo.setSeatCount(obtRoomData.getSeatNum());
        // <!--备用机数-->
        examInfo.setBackupSeatCount(obtRoomData.getBackupSeatCount());
        // <!--位置-->
        examInfo.setPosition(obtRoomData.getRoomPosition());
        //允许提前交卷时间(单位分钟，0为不允许提前交卷)
        examInfo.setAheadCallback(configMap.get(TaskConfigCodeEnum.AHEAD_SUBMIT_TIME.getCode()));
        //允许迟到时间(单位分钟， 0为不允许迟到)
        examInfo.setBeLateFor(configMap.get(TaskConfigCodeEnum.LATE_TIME.getCode()));
        //加时分钟数，单位分钟
        examInfo.setAddTimeLimit("999");
        // 允许整场考试中断的分钟数
        examInfo.setBreakTime("999");

        examInfo.setUploadDataOption(configMap.get(TaskConfigCodeEnum.UPLOAD_DATA_OPTION.getCode()));
        examInfo.setUploadDataPeriod(configMap.get(TaskConfigCodeEnum.UPLOAD_DATA_PERIOD.getCode()));
        examInfo.setWaitStartExam("999999");
        // 允许提前开考分钟数
        examInfo.setAheadStartExam(configMap.get(TaskConfigCodeEnum.AHEAD_TIME.getCode()));
        examInfo.setFtpUrl1("");
        String examClassCodeConfig = configMap.get(TaskConfigCodeEnum.EXAM_SYSTEM_TYPE.getCode());
        examInfo.setExamClassCode("OBT".equalsIgnoreCase(examClassCodeConfig) ? ExamClassCodeEnum.OBT.getCode() :
                ExamClassCodeEnum.GCE.getCode());
        examInfo.setIsOpenExtranetVisit(configMap.get(TaskConfigCodeEnum.IS_OPEN_EXTRANET_VISIT.getCode()));
        examInfo.setExamMode(task.getType().toString());
        examInfo.setUndoPassword(configMap.get(TaskConfigCodeEnum.UNDO_PASSWORD.getCode()));
        examInfo.setThroughoutThePwd(configMap.get(TaskConfigCodeEnum.THROUGHOUT_THE_PWD.getCode()));
        examInfo.setEnableBehavior(configMap.get(TaskConfigCodeEnum.ENABLE_BEHAVIOR.getCode()));
        return examInfo;
    }

    /**
     * 证券不需要
     *
     * @return 默认列表
     */
    private List<Module> getSubjectModuleList() {
        Module module = new Module();
        List<Module> list = new ArrayList<>();
        list.add(module);
        return list;
    }

    /**
     * 科目信息
     *
     * @param obtRoomData 场次
     * @return 科目信息
     */
    private List<ObtSubject> getSubjectList(String taskId, ObtRoomData obtRoomData) {
        return iTimeSubjectService.getTimeSubjectList(taskId, obtRoomData.getTimeId());

    }

    /**
     * 批次信息
     *
     * @param taskId   任务
     * @param version  版本号
     * @param obtRoomData 场次
     * @return 批次
     */
    private List<ExamTime> getExamTimesList(String taskId, String version, ObtRoomData obtRoomData) {
        List<ExamTime> examTimeList = new ArrayList<>();
        LambdaQueryWrapper<Time> queryWrapper = Wrappers.<Time>lambdaQuery()
                .eq(Time::getTaskId, taskId)
                .eq(Time::getId, obtRoomData.getTimeId())
                .eq(Time::getDelFlag, false)
                .orderByAsc(Time::getStartTime);
        List<Time> timeList = iTimeService.list(queryWrapper);
        timeList.forEach(item -> {
            ExamTime examTime = new ExamTime();
            examTime.setOrderId(obtRoomData.getOrderId());
            examTime.setName(item.getName());
            examTime.setExamTimeId(item.getCode().toString());
            // 不知道这个数据怎么来
            examTime.setPaperLang("1");
            examTime.setVersion(version);
            examTime.setDate(item.getStartTime().format(Constants.YMD));
            examTime.setStartTime(item.getStartTime().format(Constants.HM));
            examTime.setEndTime(item.getEndTime().format(Constants.HM));
            examTimeList.add(examTime);
        });
        return examTimeList;
    }

    /**
     * 座位号信息
     *
     * @param obtRoomData 场次
     * @return 座位
     */
    private List<Seat> getSeatList(String taskId, ObtRoomData obtRoomData) {
        return iTimeRoomCandidateService.getSeatList(taskId, obtRoomData.getTimeRoomId());
    }

}
